package com.enfi.api.util;

import com.enfi.api.modules.db.base.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * @author wind
 * @date 2020/4/7$
 */
@Slf4j
public class SpeUtils {
    private volatile static SpeUtils instance = null;

    public static SpeUtils getInstance() {
        //先检查实例是否存在，如果不存在才进入下面的同步块
        if (instance == null) {
            //同步块，线程安全的创建实例
            synchronized (SpeUtils.class) {
                //再次检查实例是否存在，如果不存在才真的创建实例
                if (instance == null) {
                    instance = new SpeUtils();
                }
            }

        }
        return instance;
    }

    private static Map<Class, List<Field>> fieldCache = new HashMap<>();


    public List<Predicate> toSpec(Object t, BaseQuery baseQuery, Root root, CriteriaBuilder builder) {
        MatchQuery matchQuery = baseQuery.getQuery();
        //判断缓存中是否已经存在，存在不需要再次生成，不存在需要重新生成
        Class clazz = t.getClass();
        List<Field> fields = fieldCache.get(clazz);
        if (fields == null) {
            //获取查询类Query的所有字段,包括父类字段
            fields = getAllFieldsWithRoot(clazz);
            fieldCache.put(clazz, fields);
        }
        List<Predicate> predicates = new ArrayList<>(fields.size());
        for (Field field : fields) {
            if (field.getModifiers() == 1 || field.getModifiers() == 2 || field.getModifiers() == 4) {
                String column = field.getName();
                field.setAccessible(true);
                try {
                    Object v = field.get(t);
                    Class c = field.getType();
                    if (v != null) {
                        switch (c.getSimpleName()) {
                            case "String":
                                predicates.add(builder.like(root.get(column), "%" + (String) v + "%"));
                                break;
                            case "Date":
                                Date dd=(Date)v;
                                Instant instant = dd.toInstant();
                                ZoneId zoneId = ZoneId.systemDefault();
                                LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
                                if(localDateTime.getHour()==0){
                                    Date startDate=Date.from(LocalDateTime.of(localDateTime.getYear(),localDateTime.getMonth(),
                                            localDateTime.getDayOfMonth(),0, 0,0).atZone(zoneId).toInstant());
                                    Date endDate=Date.from(LocalDateTime.of(localDateTime.getYear(),localDateTime.getMonth(),
                                            localDateTime.getDayOfMonth(),23, 59,59).atZone(zoneId).toInstant());
                                    predicates.add(builder.between(root.get(column),startDate,endDate));
                                }else {
                                    predicates.add(builder.equal(root.get(column),(Date)v));
                                }
                                break;
                            default:
                                predicates.add(builder.equal(root.get(column), v));
                        }
                    } else {
                        if (matchQuery.getAnd() != null && matchQuery.getAnd() != null) {
                            AndQuery andQuery = matchQuery.getAnd();
                            if (andQuery.getRange() != null) {
                                andQuery.getRange().entrySet().stream().filter(map -> map.getKey().equalsIgnoreCase(column))
                                        .findFirst().ifPresent(t1 -> {
                                    RangeQuery rangeQuery =  t1.getValue();
                                    if (rangeQuery.getGt() != null) {
                                        if (getType(rangeQuery.getGt()) == 1) {
                                            predicates.add(builder.gt(root.get(column), NumberUtils.createNumber(String.valueOf(rangeQuery.getGt()))));
                                        }
                                        if (getType(rangeQuery.getGt()) == 2) {
                                            predicates.add(builder.greaterThan(root.get(column), DateTimeUtils.convert((String) rangeQuery.getGt())));
                                        }
                                    }
                                    if (rangeQuery.getGte() != null) {
                                        if (getType(rangeQuery.getGte()) == 1) {
                                            predicates.add(builder.ge(root.get(column), NumberUtils.createNumber(String.valueOf(rangeQuery.getGte()))));
                                        }
                                        if (getType(rangeQuery.getGte()) == 2) {
                                            predicates.add(builder.greaterThanOrEqualTo(root.get(column),  DateTimeUtils.convert((String) rangeQuery.getGte())));
                                        }
                                    }
                                    if (rangeQuery.getLt() != null) {
                                        if (getType(rangeQuery.getLt()) == 1) {
                                            predicates.add(builder.lt(root.get(column), NumberUtils.createNumber(String.valueOf(rangeQuery.getLt()))));
                                        }
                                        if (getType(rangeQuery.getLt()) == 2) {
                                            predicates.add(builder.lessThan(root.get(column),  DateTimeUtils.convert((String) rangeQuery.getLt())));
                                        }
                                    }
                                    if (rangeQuery.getLte() != null) {
                                        if (getType(rangeQuery.getLte()) == 1) {
                                            predicates.add(builder.le(root.get(column), NumberUtils.createNumber(String.valueOf(rangeQuery.getLte()))));
                                        }
                                        if (getType(rangeQuery.getLte()) == 2) {
                                            predicates.add(builder.lessThanOrEqualTo(root.get(column),  DateTimeUtils.convert((String) rangeQuery.getLte())));
                                        }
                                    }
                                });
                            }
                            if (andQuery.getTerm() != null) {
                                andQuery.getTerm().entrySet().stream().filter(map -> map.getKey().equalsIgnoreCase(column))
                                        .findFirst().ifPresent(t1 -> {
                                    TermQuery termQuery =t1.getValue();
                                    if (termQuery.getValue() != null) {
                                        if (StringUtils.equalsIgnoreCase("eq", termQuery.getType()) || StringUtils.isBlank(termQuery.getType())) {
                                            predicates.add(builder.equal(root.get(column), termQuery.getValue()));
                                        }
                                        if (StringUtils.equalsIgnoreCase("containing", termQuery.getType())) {
                                            predicates.add(builder.like(root.get(column), "%" + (String) termQuery.getValue() + "%"));
                                        }
                                        if (StringUtils.equalsIgnoreCase("startingWith", termQuery.getType())) {
                                            predicates.add(builder.like(root.get(column), "%" + (String) termQuery.getValue()));
                                        }
                                        if (StringUtils.equalsIgnoreCase("endingWith", termQuery.getType())) {
                                            predicates.add(builder.like(root.get(column), (String) termQuery.getValue() + "%"));
                                        }
                                        if (StringUtils.equalsIgnoreCase("like", termQuery.getType())) {
                                            predicates.add(builder.like(root.get(column), (String) termQuery.getValue()));
                                        }
                                    }
                                });
                            }
                        }
                    }
                } catch (IllegalAccessException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return predicates;
    }

    private int getType(Object c) {
        if (c instanceof Integer || c instanceof Long || c instanceof Double || c instanceof Float) {
            return 1;
        }
        if (c instanceof String) {
            if (NumberValidationUtils.isRealNumber((String) c)) {
                return 1;
            }
            if (DateTimeUtils.isDateTime((String) c)) {
                return 2;
            }
        }
        return 0;
    }

    //获取类clazz的所有Field，包括其父类的Field
    private List<Field> getAllFieldsWithRoot(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Field[] dFields = clazz.getDeclaredFields();//获取本类所有字段
        if (null != dFields && dFields.length > 0)
            fieldList.addAll(Arrays.asList(dFields));
        // 若父类是Object，则直接返回当前Field列表
        Class<?> superClass = clazz.getSuperclass();
        if (superClass == Object.class) return Arrays.asList(dFields);
        // 递归查询父类的field列表
        List<Field> superFields = getAllFieldsWithRoot(superClass);
        if (null != superFields && !superFields.isEmpty()) {
            for (Field field : superFields) {
                if (!fieldList.contains(field)) {
                    fieldList.add(field);
                }
            }
        }
        return fieldList;
    }
}
